package pl.edu.agh.jemo.evolution.objfunc.impl.clustering;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import pl.edu.agh.jemo.evolution.population.Population;
import pl.edu.agh.jemo.evolution.specimen.Specimen;
import pl.edu.agh.jemo.gui.LoggerHelper;
import pl.edu.agh.jemo.gui.chart.Plot;
import pl.edu.agh.jemo.gui.chart.PlotType;
import pl.edu.agh.jemo.gui.chart.Plotter;

public class Clusterizer {

	private Population population;
	private Population[] sortedPopulations;
	private List<Window> windows = new ArrayList<Window>();

 
/*	// Michalewicz
	
	private double[] INIT_WINDOW_SIZE = { .5, .5 };
	private int INIT_WINDOW_COUNT = 140;
	private double MOVEMENT_THRESHOLD = 0.1;// max_range * threshold
	private double ENLARGMENT_INCREASE_STEP = .18;
	private double ENLARGMENT_STOP_THRESHOLD = .11;// = ENLARGMENT_INCREASE_RATIO
													// /
													// INIT_WINDOW_POPULATION_RATIO;
	private double MERGE_RATIO = .3;
	private double MERGE_DISREGARD_RATIO = .95;
	private double MIN_WINDOW_DISTANCE = .2;
*/	

/*	// Michalewicz dla samego extrema findera
	
	private double[] INIT_WINDOW_SIZE = { .6, .6 };
	private int INIT_WINDOW_COUNT = 100;
	private double MOVEMENT_THRESHOLD = 0.05;// max_range * threshold
	private double ENLARGMENT_INCREASE_STEP = .1;
	private double ENLARGMENT_STOP_THRESHOLD = .1;// = ENLARGMENT_INCREASE_RATIO
													// /
													// INIT_WINDOW_POPULATION_RATIO;
	private double MERGE_RATIO = .3;
	private double MERGE_DISREGARD_RATIO = 1.;
*/
	
	
	// Rastrigin

/*	private double[] INIT_WINDOW_SIZE = { .1, .1 };
	private int INIT_WINDOW_COUNT = 500;
	private double MOVEMENT_THRESHOLD = 0.1;// max_range * threshold
	private double ENLARGMENT_INCREASE_STEP = .1;
	private double ENLARGMENT_STOP_THRESHOLD = .15;// = ENLARGMENT_INCREASE_RATIO
													// /
													// INIT_WINDOW_POPULATION_RATIO;
	private double MERGE_RATIO = .65;
	private double MERGE_DISREGARD_RATIO = 1.;
	
	private int dimensions;

	public void setPopulationReference(Population population) {
		this.population = population;
		dimensions = population.getDimensions();
		sortPopulation();
	}*/
	
	//INNY RASTRIGIN
	private double[] INIT_WINDOW_SIZE = { .05, .05 };
	private int INIT_WINDOW_COUNT = 1000;
	private double MOVEMENT_THRESHOLD = 0.1;// max_range * threshold
	private double ENLARGMENT_INCREASE_STEP = .05;
	private double ENLARGMENT_STOP_THRESHOLD = .15;// = ENLARGMENT_INCREASE_RATIO
													// /
													// INIT_WINDOW_POPULATION_RATIO;
	private double MERGE_RATIO = .65;
	private double MERGE_DISREGARD_RATIO = 1.;
	
	private int dimensions;

	public void setPopulationReference(Population population) {
		this.population = population;
		dimensions = population.getDimensions();
		sortPopulation();
	}

	private void sortPopulation() {
		sortedPopulations = new Population[dimensions];

		for (int i = 0; i < dimensions; i++) {
			sortedPopulations[i] = new Population();
			sortedPopulations[i].setObjectiveFunctionSet(population
					.getObjectiveFunctionSet());
			sortedPopulations[i].addAll(population);
			Collections.sort(sortedPopulations[i], Population
					.getSpecimenDimensionGenotypeComparator(i));
		}
	}

	private void createClusters() {
		initializeWindows();
		plotThis("Clusters-init");

		moveWindows();
		plotThis("Clusters-move");
		enlargeWindows();
		plotThis("Clusters-enlarge");
		mergeWindows();
		plotThis("Clusters-merge");
		
	}
	
	public ArrayList<Population> getClusters() {
		createClusters();
		ArrayList<Population> retList = new ArrayList<Population>();
		Set<Specimen> retSet = new HashSet<Specimen>();
		Population currentWindow = new Population();
		Population pop = new Population();
		
		for (Window w: windows) {
			currentWindow.clear();
			pop = new Population();
			currentWindow.addAll(w.getSpecimensInside());
			System.out.println("Window size " + w.getSpecimensInside().size());
			for(Specimen spec : currentWindow) {
				if(!retSet.contains(spec)) {
					pop.add(spec);
					retSet.add(spec);
				}
			}
			retList.add(pop);
		}
		
		//osobniki poza klastrami
		for (Specimen spec : population) {
			if (!retSet.contains(spec)) {
				//retSet.add(spec);
				pop = new Population();
				pop.add(spec);
				retList.add(pop);
			}
		}
		
		return retList;
	}
	
	public Map<Specimen, Double> clusterize() {
		createClusters();
		return assignMap();
	}

	private Map<Specimen, Double> assignMap() {
		Map<Specimen, Double> retMap = new HashMap<Specimen, Double>();

		Double maxWindowSize = 0.0;
		for(Window w : windows) {
			if(w.getSpecimensInside().size() > maxWindowSize) {
				maxWindowSize = w.getSpecimensInside().size()*1.;
			}
		}

		// posortowac okna po odleglosci osobnikow od srodka okna
		List<Specimen> sortedWindows = new ArrayList<Specimen>();
		for(final Window w : windows) {
			sortedWindows.clear();
			sortedWindows.addAll(w.getSpecimensInside());

			//na poczatku najwieksza wartosc
			Collections.sort(sortedWindows, new Comparator<Specimen>() {
				@Override
				public int compare(Specimen o1, Specimen o2) {
					Double[] center = new Double[dimensions];
					w.centersDistance(center);
					double o1Distance = o1.getEuclideanGenotypeSpaceDistance(center);
					double o2Distance = o2.getEuclideanGenotypeSpaceDistance(center);

					if (o1Distance > o2Distance) {
						return -1;
					}

					if (o1Distance < o2Distance) {
						return 1;
					}
					return 0;
				}
			});
			
			System.out.println("windows size: " +sortedWindows.size());
			for (int i = 0; i < sortedWindows.size(); i++) {
				Double currentValue = retMap.get(sortedWindows.get(i));
				Double maxValue = -i * 1. - 1.;
				if(currentValue!=null && currentValue>maxValue) continue;
				retMap.put(sortedWindows.get(i), currentValue );
				
			}
		}
		
		int counter = 0;
		for (Specimen spec : population) {
			if (!retMap.containsKey(spec)) {
				counter++;
				retMap.put(spec, 0.);
			}
		}
		System.out.println("poza klastrami: " + counter);

		for (Specimen spec : population) {
			if (!retMap.containsKey(spec)) {
				System.err.println("poza klastrami: " + spec);
			}
		}
		
		if (retMap.keySet().size() != population.size()) {
			System.err.println("Ret map ma za mao specow " + retMap.keySet().size());
			
		}
		
		return retMap;
	}

	// okna w ekstremach
	void initializeWindows() {
		windows.clear();

		Population unassignedPopulation = new Population();
		unassignedPopulation.addAll(population);

		Collections.sort(unassignedPopulation, Population
				.getSpecimenPhenotype0Comparator());
//		Collections.reverse(unassignedPopulation);
		while ((windows.size() < INIT_WINDOW_COUNT)
				&& (unassignedPopulation.size() > 0)) {
			Double[][] windowBoundaries = new Double[dimensions][2];
			for (int i = 0; i < dimensions; i++) {
				windowBoundaries[i][0] = unassignedPopulation.get(0)
						.getGenotype()[i].asDouble()
						- (INIT_WINDOW_SIZE[i] / 2);
				windowBoundaries[i][1] = unassignedPopulation.get(0)
						.getGenotype()[i].asDouble()
						+ (INIT_WINDOW_SIZE[i] / 2);
			}

			Window newWindow = new Window(sortedPopulations, windowBoundaries);
			//unassignedPopulation.remove(0);
			unassignedPopulation.removeAll(newWindow.getSpecimensInside());

/*			for (int i = 0; i < unassignedPopulation.size(); i++) {
				if (unassignedPopulation.get(i)
						.getEuclideanGenotypeSpaceDistance(lastSpec) < MIN_WINDOW_DISTANCE) {
					unassignedPopulation.remove(i--);
				}
			}
*/
			if (newWindow.getSpecimensInside().size() > 0) {
				windows.add(newWindow);
			}
		}
	}

	protected void moveWindows() {
		for (Window w : windows)
			w.move(MOVEMENT_THRESHOLD);
	}

	private void enlargeWindows() {
		for (Window w : windows) {
			w.enlarge(ENLARGMENT_INCREASE_STEP, ENLARGMENT_STOP_THRESHOLD,
					MOVEMENT_THRESHOLD);
		}
	}

	protected void mergeWindows() {

		int i = 0;
		int j = 0;
		while (i < windows.size() - 1) {
			if (!windows.get(i).isDisregard()) {
				j = i + 1;
				while (j < windows.size()) {
					if (!windows.get(j).isDisregard()) {

						int overlaping = windows.get(i).getOverlapingCount(
								windows.get(j));
						if ((1. * overlaping)
								/ windows.get(i).getSpecimensInside().size() >= MERGE_DISREGARD_RATIO) {// i
							if (windows.get(i).getSpecimensInside().size() <= windows
									.get(j).getSpecimensInside().size()) { // i<=j
								windows.get(i).setDisregard(true);
							} else { // i>j
								windows.get(j).setDisregard(true);
							}
						} else if ((1. * overlaping)
								/ windows.get(j).getSpecimensInside().size() >= MERGE_DISREGARD_RATIO) { // j
							if (windows.get(i).getSpecimensInside().size() <= windows
									.get(j).getSpecimensInside().size()) { // i<=j
								windows.get(i).setDisregard(true);
							} else { // i>j
								windows.get(j).setDisregard(true);
							}
						}

						if ((((double) overlaping)
								/ windows.get(i).getSpecimensInside().size() + ((double) overlaping)
								/ windows.get(j).getSpecimensInside().size()) / 2.0 >= MERGE_RATIO) {
							// merge
							windows.get(j).getMergeList().add(windows.get(i));
							windows.get(i).getMergeList().add(windows.get(j));
						}
					}
					j++;
				}
			}
			i++;
		}

		// disregard
		for (i = 0; i < windows.size(); i++) {
			if (windows.get(i).isDisregard()) {
				for (Window w : windows.get(i).getMergeList()) {
					w.getMergeList().remove(windows.get(i));
				}
				windows.remove(i--);
			}
		}

		// merge
		/**
		 * Leci po oknach, jesli okna nie ma w tej o mapie ponizej, to nie bylo
		 * mergowane Jesli jest, to znaczy, ze jego zawartosc zostala wrzucona
		 * do okna, na ktore wskazuje mapa dlatego merguje sie z oknem, ktore
		 * wskazuje mapa, a nie tym o. dodaje do okna osobniki z okien z merdz
		 * listy, a potem disregarduje okna w mapie
		 */
		Map<Window, Window> mergedMap = new HashMap<Window, Window>();
		for (i = 0; i < windows.size(); i++) {
			Window w = windows.get(i);
			if (mergedMap.get(w) != null)
				continue;
			for (Window c : w.getMergeList()) {
				Window toMergeWindow = c;
				while (mergedMap.containsKey(toMergeWindow)) {
					toMergeWindow = mergedMap.get(toMergeWindow);
				}
				if (toMergeWindow != w) {
					w.getSpecimensInside().addAll(
							toMergeWindow.getSpecimensInside());
					mergedMap.put(toMergeWindow, w);
				}
			}
		}
		windows.removeAll(mergedMap.keySet());
	}

	private void plotThis(String name) {
		try {
			LoggerHelper.getInstance().getLogger().debug(name + "Ready");
			Plotter plotter = Plotter.getPlotter();
			Plot plot = new Plot();
			plot.setName(name);
			plot.setPlotType(PlotType.GENOTYPE);
			Population empty = new Population();
			empty.addAll(population);

			for (Window w : windows) {
				Population wp = new Population();
				wp.addAll(w.getSpecimensInside());
				empty.retainAll(wp);
				plot.addPopulation(wp);
			}
			plot.addPopulation(empty);
			plotter.addPlot(plot);

		} catch (IOException e) {
			// Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public String toString() {
/*		private double[] INIT_WINDOW_SIZE = { .4, .4 };
		private int INIT_WINDOW_COUNT = 500;
		private double MOVEMENT_THRESHOLD = 0.1;// max_range * threshold
		private double ENLARGMENT_INCREASE_STEP = .08;
		private double ENLARGMENT_STOP_THRESHOLD = .2;// = ENLARGMENT_INCREASE_RATIO
														// /
														// INIT_WINDOW_POPULATION_RATIO;
		private double MERGE_RATIO = .3;
		private double MERGE_DISREGARD_RATIO = .95;
*/
		StringBuilder sb = new StringBuilder();
		sb.append(String.format("Clustering: init window size [%f,%f]", INIT_WINDOW_SIZE[0], INIT_WINDOW_SIZE[1]));
		sb.append(String.format("\nClustering: init window count %d", INIT_WINDOW_COUNT));
		sb.append(String.format("\nClustering: movement threshold %f", MOVEMENT_THRESHOLD));
		sb.append(String.format("\nClustering: enlargement increase step %f", ENLARGMENT_INCREASE_STEP));
		sb.append(String.format("\nClustering: enlargement stop threshold %f", ENLARGMENT_STOP_THRESHOLD));
		sb.append(String.format("\nClustering: merge ratio %f", MERGE_RATIO));
		sb.append(String.format("\nClustering: merge disregard ratio %f", MERGE_DISREGARD_RATIO));
		
		return sb.toString();
	}
}
